#include <string.h>
#include <assert.h>
#include "vnode.h"
#include "vnode_map.h"

void unlink_vnode(VNode *vnode)
{
	if(--vnode->m_nlinks <= 0) {
		delete vnode;
	}
}


VNode::VNode()
{
	m_type = Type::unknown;
	m_parent = nullptr;
	m_nlinks = 0;
}

void VNode::set_name(const char *name)
{
	m_name = std::string(name);
}

const char *VNode::get_name() const
{
	return m_name.c_str();
}

VNode::Type VNode::get_type() const
{
	return m_type;
}

bool VNode::is_dir() const
{
	return m_type == Type::dir;
}

bool VNode::is_file() const
{
	return m_type == Type::file;
}

bool VNode::is_mountpoint() const
{
	return false;
}

bool VNode::is_real() const
{
	return false;
}

VNode *VNode::get_parent()
{
	return m_parent;
}

const VNode *VNode::get_parent() const
{
	return m_parent;
}



// ---- VNodeDir ----

VNodeDir::VNodeDir()
{
	m_type = Type::dir;
	m_dirlink = nullptr;
}

VNodeDir::~VNodeDir()
{
	if(m_dirlink) {
		unlink_vnode(m_dirlink);
	}
}

VNodeDir *VNodeDir::get_dirlink() const
{
	return m_dirlink;
}

bool VNodeDir::is_mountpoint() const
{
	return m_dirlink != nullptr;
}

bool VNodeDir::add_child(VNode *node)
{
	assert(node);

	int idx = get_child_index(node->m_name.c_str());
	if(idx != -1) {
		// we already have a child with that name
		if(m_child[idx] == node) {
			return true;	// and it's the same node
		}
		return false;	// trying to add a different node with the same name
	}

	idx = -1;
	try {
		m_child.push_back(node);
		idx = m_child.size() - 1;
		m_child_idx[node->m_name] = idx;
	}
	catch(...) {
		// in case the index insertion was what failed
		if(idx != -1) {
			m_child.pop_back();
		}
		return false;
	}

	node->m_parent = this;
	node->m_nlinks++;
	return true;
}

int VNodeDir::remove_child(int idx)
{
	if(idx < 0 || idx >= (int)m_child.size()) {
		return -1;
	}

	VNode *node = m_child[idx];
	m_child.erase(m_child.begin() + idx);
	m_child_idx.erase(node->m_name);
	return idx;
}

int VNodeDir::remove_child(const char *name)
{
	int idx = get_child_index(name);
	if(idx == -1) {
		return -1;
	}

	assert(idx >= 0 && idx < (int)m_child.size());
	m_child.erase(m_child.begin() + idx);
	return idx;
}

VNode *VNodeDir::get_child(int idx)
{
	if(idx < 0 || idx >= (int)m_child.size()) {
		return nullptr;
	}
	return m_child[idx];
}

const VNode *VNodeDir::get_child(int idx) const
{
	if(idx < 0 || idx >= (int)m_child.size()) {
		return nullptr;
	}
	return m_child[idx];
}

VNode *VNodeDir::get_child(const char *name)
{
	int idx = get_child_index(name);
	if(idx == -1) {
		return nullptr;
	}

	assert(idx >= 0 && idx < (int)m_child.size());
	return m_child[idx];
}

const VNode *VNodeDir::get_child(const char *name) const
{
	int idx = get_child_index(name);
	if(idx == -1) {
		return nullptr;
	}

	assert(idx >= 0 && idx < (int)m_child.size());
	return m_child[idx];
}

int VNodeDir::get_child_index(const char *name) const
{
	if(!name) return -1;

	auto res = m_child_idx.find(name);
	if(res == m_child_idx.end()) {
		return -1;
	}
	return res->second;
}

bool VNodeDir::mount(const char *path)
{
	// let's see if it's a real directory
	VNodeDirMap *dirmap = new VNodeDirMap;
	if(dirmap->open(path)) {
		return mount(dirmap);
	}
	delete dirmap;

	// nothing else is implemented at the moment ...
	return false;	// TODO
}

bool VNodeDir::mount(VNodeDir *vdir)
{
	vdir->m_nlinks++;
	vdir->m_parent = m_parent;
	m_dirlink = vdir;
	return true;
}

bool VNodeDir::umount()
{
	if(!m_dirlink) {
		return false;
	}
	unlink_vnode(m_dirlink);
	m_dirlink = nullptr;
	if(m_dirlink->m_parent == this) {
		m_dirlink->m_parent = nullptr;	// XXX ok this will be dodgy with multiple links ...
	}
	return true;
}

VNode *VNodeDir::lookup(char *path)
{
	VNodeDir *vdir = this;	// start from this directory

	while(*path) {
		char *nextslash = strchr(path, '/');
		if(nextslash) *nextslash = 0;

		// this directory is actually a mount point. we need to continue from the target...
		if(vdir->is_mountpoint()) {
			VNodeDir *target = vdir->get_dirlink();
			return target->lookup(path);
		}

		VNode *node = vdir->get_child(path);
		if(!node) return nullptr;

		if(nextslash) {
			if(node->is_file()) {
				return nullptr;	// expected a directory but we found a file
			}
			assert(node->is_dir());

			vdir = (VNodeDir*)node;	// continue lookup in this next directory
		} else {
			return node;	// found it...
		}
	}

	return vdir;	// run out of path, this must be it...
}

// ---- VNodeFile ----

VNodeFile::VNodeFile()
{
	m_type = Type::file;
}
